/*
 * DexProto.c
 *
 *  Created on: 2011-7-20
 *      Author: gexiao
 */

/*
 * Functions for dealing with method prototypes
 */
#include "DexProto.h"

#include <stdlib.h>
#include <string.h>

/*
 * ==========================================
 * 		Method Prototypes
 * ==========================================
 */

/*
 * Return the DexProtoId from the given DexProto. The DexProto must
 * actually refer to a DexProtoId.
 */
static inline const DexProtoId* getProtoId(const DexProto* pProto) {
	return dexGetProtoId(pProto->dexFile, pProto->protoIdx);
}

/*
 * Get the short-form method descriptor for the given prototype. The
 * prototype must be protoIdx-based.
 */
const char* dexProtoGetShorty(const DexProto* pProto) {
	const DexProtoId* protoId = getProtoId(pProto);

	return dexStringById(pProto->dexFile, protoId->shortyIdx);
}

/*
 * Get the type descriptor for the return type of the given prototype.
 */
const char* dexProtoGetReturnType(const DexProto* pProto) {
	const DexProtoId* protoId = getProtoId(pProto);
	return dexStringByTypeIdx(pProto->dexFile, protoId->returnTypeIdx);
}

/*
 * Get the parameter count of the given prototype.
 */
size_t dexProtoGetParameterCount(const DexProto* pProto) {
	const DexProtoId* protoId = getProtoId(pProto);
	const DexTypeList* typeList = dexGetProtoParameters(pProto->dexFile,
			protoId);
	return (typeList == NULL) ? 0 : typeList->size;
}

/*
 * Compute the number of parameter words (u4 units) required by the
 * given prototype. For example, if the method takes (int, long) and
 * returns double, this would return 3 (one for the int, two for the
 * long, and the return type isn't relevant).
 */
int dexProtoComputeArgsSize(const DexProto* pProto) {
	const char* shorty = dexProtoGetShorty(pProto);
	int count = 0;

	//Skip the return type.
	shorty++;

	for (;;) {
		switch (*(shorty++)) {
		case '\0': {
			return count;
		}
		case 'D':
		case 'J': {
			count += 2;
			break;
		}
		default: {
			count++;
			break;
		}
		}
	}
}

/*
 * Compare the two prototypes. The two prototypes are compared
 * with the return type as the major order, then the first arguments,
 * then second, etc. If two prototypes are identical except that one
 * has extra arguments, then the shorter argument is considered the
 * earlier one in sort order (similar to strcmp()).
 */
int dexProtoCompare(const DexProto* pProto1, const DexProto* pProto2) {
	if (pProto1 == pProto2) {
		// Easy out.
		return 0;
	} else {
		const DexFile* dexFile1 = pProto1->dexFile;
		const DexProtoId* protoId1 = getProtoId(pProto1);
		const DexTypeList* typeList1 =
				dexGetProtoParameters(dexFile1, protoId1);
		int paramCount1 = (typeList1 == NULL) ? 0 : typeList1->size;

		const DexFile* dexFile2 = pProto2->dexFile;
		const DexProtoId* protoId2 = getProtoId(pProto2);
		const DexTypeList* typeList2 =
				dexGetProtoParameters(dexFile2, protoId2);
		int paramCount2 = (typeList2 == NULL) ? 0 : typeList2->size;

		if (protoId1 == protoId2) {
			//Another easy out.
			return 0;
		}

		//Compare return types.

		int result = strcmp(
				dexStringByTypeIdx(dexFile1, protoId1->returnTypeIdx),
				dexStringByTypeIdx(dexFile2, protoId2->returnTypeIdx));

		if (result != 0) {
			return result;
		}

		//Compare parameters.
		int minParam = (paramCount1 > paramCount2) ? paramCount2 : paramCount1;
		int i;

		for (i = 0; i < minParam; i++) {
			u4 idx1 = dexTypeListGetIdx(typeList1, i);
			u4 idx2 = dexTypeListGetIdx(typeList2, i);
			int result = strcmp(dexStringByTypeIdx(dexFile1, idx1),
					dexStringByTypeIdx(dexFile2, idx2));

			if (result != 0) {
				return result;
			}
		}

		if (paramCount1 < paramCount2) {
			return -1;
		} else if (paramCount1 > paramCount2) {
			return 1;
		} else {
			return 0;
		}
	}
}

/*
 * Helper for dexProtoCompareToDescriptor(), which gets the return type
 * descriptor from a method descriptor string.
 */
static const char* methodDescriptorReturnType(const char* descriptor) {
	const char* result = strchr(descriptor, ')');

	if (result == NULL) {
		return NULL;
	}

	//The return type is the character just past the ')'.
	return result + 1;
}

/*
 * Helper for dexProroCompareToDescriptor(), which indicates the end
 * of an embedded argument type descriptor,which is also the
 * beginning of the next argument type descriptor. Since this is for
 * argument types, it doesn't accept 'V' as a valid type descriptor.
 */
static const char* methodDescriptorNextType(const char* descriptor) {
	//Skip any array references.

	while (*descriptor == '[') {
		descriptor++;
	}

	switch (*descriptor) {
	case 'B':
	case 'C':
	case 'D':
	case 'F':
	case 'I':
	case 'J':
	case 'S':
	case 'Z': {
		return descriptor + 1;
	}
	case 'L': {
		const char* result = strchr(descriptor + 1, ';');
		if (result != NULL) {
			//The type ends just past the ';'.
			return result + 1;
		}
	}
	}
	return NULL;
}

/*
 * Compare a prototype and a string method descriptor. The comparison
 * is done as if the descriptor were converted to a prototype and compared
 * with dexProtoCompare().
 */
int dexProtoCompareToDescriptor(const DexProto* proto, const char* descriptor) {
	//First compare the return types.

	int result = strcmp(dexProtoGetReturnType(proto),
			methodDescriptorReturnType(descriptor));

	if (result != 0) {
		return result;
	}

	//The return types match, so we have to check arguments.

	DexParameterIterator iterator;
	dexParameterIteratorInit(&iterator, proto);

	//Skip the '('.
	assert(*descriptor == '(');
	descriptor++;

	for (;;) {
		const char* protoDesc = dexParameterIteratorNextDescriptor(&iterator);

		if (*descriptor == ')') {
			//It's the end of the descriptor string.
			if (protoDesc == NULL) {
				//It's also the end of the prototype's arguments.
				return 0;
			} else {
				//The prototype still has more arguments.
				return 1;
			}
		}

		if (protoDesc == NULL) {
			/*
			 * The prototype doesn't have arguments left, but the
			 * descriptor string does.
			 */
			return -1;
		}

		// Both prototype and descriptor have arguments. Compare them.

		const char* nextDesc = methodDescriptorNextType(descriptor);

		for (;;) {
			char c1 = *(protoDesc++);
			char c2 = (descriptor < nextDesc) ? *(descriptor++) : '\0';

			if (c1 < c2) {
				// This includes the case where the proto is shorter.
				return -1;
			} else if (c1 > c2) {
				// This includes the case where the desc is shorter.
				return 1;
			} else if (c1 == '\0') {
				// The two types are equal in length. (c2 necessarily == '\0'.)
				break;
			}
		}

		/*
		 * If we made it here, the two arguments matched, and
		 * descriptor == nextDesc.
		 */
	}
}

/*
 * =========================================================
 * 		Parameter Iterators
 * =========================================================
 */

/*
 * Initialize the given DexParameterIterator to be at the start of the
 * parameters of the given prototype.
 */
void dexParameterIteratorInit(DexParameterIterator* pIterator,
		const DexProto* pProto) {
	pIterator->proto = pProto;
	pIterator->cursor = 0;
	pIterator->parameters = dexGetProtoParameters(pProto->dexFile,
			getProtoId(pProto));
	pIterator->parameterCount = (pIterator->parameters == NULL) ? 0
			: pIterator->parameters->size;
}

/*
 * Get the type_id index for the next parameter, if any. This returns
 * kDexNoIndex if the last parameter has already been consumed.
 */
u4 dexParameterIteratorNextIndex(DexParameterIterator* pIterator) {
	int cursor = pIterator->cursor;
	int parameterCount = pIterator->parameterCount;

	if (cursor >= parameterCount) {
		// The iteration is complete.
		return kDexNoIndex;
	} else {
		u4 idx = dexTypeListGetIdx(pIterator->parameters, cursor);
		pIterator->cursor++;
		return idx;
	}
}

/*
 * Get the type descriptor for the next parameter, if any. This returns
 * NULL if the last parameter has already been consumed.
 */
const char* dexParameterIteratorNextDescriptor(DexParameterIterator* pIterator) {
	u4 idx = dexParameterIteratorNextIndex(pIterator);

	if (idx == kDexNoIndex) {
		return NULL;
	}

	return dexStringByTypeIdx(pIterator->proto->dexFile, idx);
}
